package com.whcoding.test.common;

/**
 * 经纬度转换工具类<br/>
 * <b>84<->02<->09<b/>
 */
public class LonLatConvertUtil {
    public static double pi = 3.1415926535897932384626;
    public static double a = 6378245.0;
    public static double ee = 0.00669342162296594323;
    public static double threshold = 0.000001;;
    /**
     * 84 to 火星坐标系 (GCJ-02) World Geodetic System ==> Mars Geodetic System
     *
     * @param lat
     * @param lon
     * @return
     */
    public static Gps gps84ToGcj02(double lat, double lon) {
        if (outOfChina(lat, lon)) {
            return null;
        }
        double dLat = transformLat(lon - 105.0, lat - 35.0);
        double dLon = transformLon(lon - 105.0, lat - 35.0);
        double radLat = lat / 180.0 * pi;
        double magic = Math.sin(radLat);
        magic = 1 - ee * magic * magic;
        double sqrtMagic = Math.sqrt(magic);
        dLat = (dLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * pi);
        dLon = (dLon * 180.0) / (a / sqrtMagic * Math.cos(radLat) * pi);
        double mgLat = lat + dLat;
        double mgLon = lon + dLon;
        return new Gps(mgLat, mgLon);
    }
    /**
     * * 火星坐标系 (GCJ-02) to 84
     * @param lon
     * @param lat
     * @return
     * */
    public static Gps gcj02ToGps84(double lat, double lon) {
        return transform(lat, lon);
    }
    /**
     * （精确） 火星坐标系 (GCJ-02) to 84
     * @param lon
     * @param lat
     * @return
     * */
    public static Gps gcj02ToGps84Exact(double lat, double lon) {
        double dLat = 0.01;
        double dLon = 0.01;
        double mLat = lat - dLat;
        double mLon = lon - dLon;
        double pLat = lat + dLat;
        double pLon = lon + dLon;
        double wgsLat = 0;
        double wgsLon = 0;
        for(int i = 0; i < 1000; i++) {
            wgsLat = (mLat + pLat) / 2;
            wgsLon = (mLon + pLon) / 2;
            Gps gps = gcj02ToGps84(wgsLat, wgsLon);
            dLat = gps.lat - lat;
            dLon = gps.lon - lon;
            if ((Math.abs(dLat) < threshold) && (Math.abs(dLon) < threshold))
                break;
            if (dLat > 0)
                pLat = wgsLat;
            else
                mLat = wgsLat;
            if (dLon > 0)
                pLon = wgsLon;
            else
                mLon = wgsLon;
        }

        return new Gps(wgsLat, wgsLon);
    }

    /**
     * 火星坐标系 (GCJ-02) 与百度坐标系 (BD-09) 的转换算法 将 GCJ-02 坐标转换成 BD-09 坐标
     *
     * @param gglat
     * @param gglon
     */
    public static Gps gcj02ToBd09(double gglat, double gglon) {
        double x = gglon;
        double y = gglat;
        double z = Math.sqrt(x * x + y * y) + 0.00002 * Math.sin(y * pi);
        double theta = Math.atan2(y, x) + 0.000003 * Math.cos(x * pi);
        double bd_lon = z * Math.cos(theta) + 0.0065;
        double bd_lat = z * Math.sin(theta) + 0.006;
        return new Gps(bd_lat, bd_lon);
    }
    /**
     * 火星坐标系 (GCJ-02) 与百度坐标系 (BD-09) 的转换算法
     * 将 BD-09 坐标转换成GCJ-02 坐标
     * @param bdlat
     * @param bdlon
     * @return
     */
    public static Gps bd09ToGcj02(double bdlat, double bdlon) {
        double x = bdlon - 0.0065;
        double y = bdlat - 0.006;
        double z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * pi);
        double theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * pi);
        double gglon = z * Math.cos(theta);
        double gglat = z * Math.sin(theta);
        return new Gps(gglat, gglon);
    }

    /**
     * 是否超出中国
     * @param lat
     * @param lon
     * @return
     */
    public static boolean outOfChina(double lat, double lon) {
        if (lon < 72.004 || lon > 137.8347)
            return true;
        if (lat < 0.8293 || lat > 55.8271)
            return true;
        return false;
    }

    /**
     *
     * @param lat
     * @param lon
     * @return
     */
    public static Gps transform(double lat, double lon) {
        if (outOfChina(lat, lon)) {
            return new Gps(lat, lon);
        }
        double dLat = transformLat(lon - 105.0, lat - 35.0);
        double dLon = transformLon(lon - 105.0, lat - 35.0);
        double radLat = lat / 180.0 * pi;
        double magic = Math.sin(radLat);
        magic = 1 - ee * magic * magic;
        double sqrtMagic = Math.sqrt(magic);
        dLat = (dLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * pi);
        dLon = (dLon * 180.0) / (a / sqrtMagic * Math.cos(radLat) * pi);
        double mgLat = lat + dLat;
        double mgLon = lon + dLon;
        return new Gps(mgLat, mgLon);
    }

    public static double transformLat(double x, double y) {
        double ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y
                + 0.2 * Math.sqrt(Math.abs(x));
        ret += (20.0 * Math.sin(6.0 * x * pi) + 20.0 * Math.sin(2.0 * x * pi)) * 2.0 / 3.0;
        ret += (20.0 * Math.sin(y * pi) + 40.0 * Math.sin(y / 3.0 * pi)) * 2.0 / 3.0;
        ret += (160.0 * Math.sin(y / 12.0 * pi) + 320 * Math.sin(y * pi / 30.0)) * 2.0 / 3.0;
        return ret;
    }

    public static double transformLon(double x, double y) {
        double ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1
                * Math.sqrt(Math.abs(x));
        ret += (20.0 * Math.sin(6.0 * x * pi) + 20.0 * Math.sin(2.0 * x * pi)) * 2.0 / 3.0;
        ret += (20.0 * Math.sin(x * pi) + 40.0 * Math.sin(x / 3.0 * pi)) * 2.0 / 3.0;
        ret += (150.0 * Math.sin(x / 12.0 * pi) + 300.0 * Math.sin(x / 30.0
                * pi)) * 2.0 / 3.0;
        return ret;
    }

    public static String encodeLatLon(double lat, double lon) {
        if (lon > 180) {
            lon += 360;
        }
        double dLat = lat * 100000;
        double dLon = lon * 100000;
        int iLat = (int) dLat;
        int iLon = (int) dLon;

        String rpB = Integer.toHexString(iLon - iLat + 8890);
        String ie9 = Integer.toHexString(iLon + iLat);

        StringBuilder H1433 = new StringBuilder();
        if (iLon < 0) {
            H1433.append("X");
        }
        if (iLat < 0) {
            H1433.append("Y");
        }
        H1433.append(get16To36String(rpB));
        H1433.append("z");
        H1433.append(get16To36String(ie9));
        H1433.append(H1433.subSequence(2,3));
        return H1433.toString().toUpperCase();
    }

    private static StringBuilder get16To36String(String rpB) {
        StringBuilder sb = new StringBuilder();
        for (char c : rpB.toCharArray()) {
            int k6A36 = Integer.valueOf(c+"", 16);
            if (k6A36 >= 10) {
                k6A36 = k6A36 + 7;
            }
            k6A36 += 10;
            sb.append(getDatafrom10(k6A36, 36));
        }
        return sb;
    }

    /**
     * 进制转换（从ASCII表的48-57，及65以后）
     * @param d
     * @param system
     * @return
     */
    private static String getDatafrom10(int d, int system) {
        byte i;
        StringBuilder s = new StringBuilder();
        char c;
        do {
            i = (byte) (d % system);
            if (i > 9) i += 55;
            else i += 48;
            c = (char) i;
            s.insert(0, c);
            d = d / system;
        }
        while (d > 0);
        return s.toString();
    }

    public static void main(String[] args) {
        double lat = 39.13431,lon = 117.29427;
//        System.out.println(gcj02ToGps84(lat, lon));
//        System.out.println(gcj02ToGps84Exact(lat, lon));
        System.out.println(encodeLatLon(22.629524,113.806766));
        System.out.println(encodeLatLon(34.308565,108.924598));
    }
    /**
     * 坐标对象
     */
    public static class Gps{

        private double lat;
        private double lon;

        public Gps() {
        }

        public Gps(double lat, double lon) {
            this.lat = lat;
            this.lon = lon;
        }

        public double getLat() {
            return lat;
        }

        public void setLat(double lat) {
            this.lat = lat;
        }

        public double getLon() {
            return lon;
        }

        public void setLon(double lon) {
            this.lon = lon;
        }

        @Override
        public String toString() {
            return "Gps{" +
                    "lat=" + lat +
                    ", lon=" + lon +
                    '}';
        }
    }
}
